﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Xml.XPath;
using System.Xml.Linq;

namespace Jukebox
{
    /// <summary>
    /// This is the base class for the jukebox state machine
    /// </summary>
    public abstract class State
    {
        protected JukeboxInstance mJukebox;
        public State(JukeboxInstance jukebox)
        {
            mJukebox = jukebox;
            mJukebox.ChangeState(this);
        }
        public virtual Guid? AddSong(SongData song) { return null; }
        public virtual void CancelSong(Guid id) { }
        public virtual void AddCommercial(CommercialData commercial) { }
        public virtual void CancelCommercial() { }
        public virtual bool Update() { return true; }
        public virtual XElement GetPlaylist() { return new XElement("playlist"); }
        public TimeSpan GetTimeout() { return new TimeSpan(0); }
        public void Stop() { new Idle(mJukebox); }
    }
    /// <summary>
    /// The Idle state is when neither commercial or songs are playing
    /// </summary>
    public class Idle : State
    {
        public Idle(JukeboxInstance jukebox) : base(jukebox) { }
        public override Guid? AddSong(SongData song)
        {
            State nextState = new Playing(mJukebox);
            return nextState.AddSong(song);
        }
        public override void AddCommercial(CommercialData commercial)
        {
            State nextState = new Commercial(mJukebox);
            nextState.AddCommercial(commercial);
        }
        public override bool Update()
        {
            return true;
        }
    }
    /// <summary>
    /// The playing state is when the jukebox is playing a requested song.
    /// When the jukebox is done with all the songs in the playlist it will return to the Idle state
    /// </summary>
    public class Playing : State
    {
        public Playing(JukeboxInstance jukebox) : base(jukebox) { }
        Queue<KeyValuePair<Guid, SongData>> mPlaylist = new Queue<KeyValuePair<Guid, SongData>>();
        Commercial mCommercial = null;
        DateTime mEndSong = DateTime.Now;
        public override Guid? AddSong(SongData song)
        {
            Guid guid = Guid.NewGuid();
            if (mPlaylist.Count == 0)
            {
                mEndSong = DateTime.Now + song.Duration;
            }
            mPlaylist.Enqueue(new KeyValuePair<Guid, SongData>(guid, song));
            return guid;
        }
        public override void AddCommercial(CommercialData commercial)
        {
            mCommercial = new Commercial(mJukebox, this);
            mEndSong += commercial.Duration;
            mCommercial.AddCommercial(commercial);
        }
        public override bool Update()
        {
            if (mEndSong > DateTime.Now) return true;
            mPlaylist.Dequeue();
            
            if (mPlaylist.Count == 0)
            {
                new Idle(mJukebox);
                return true;
            }

            KeyValuePair<Guid, SongData> element = mPlaylist.First();
            mEndSong += element.Value.Duration;
            return true;
        }
        public override void CancelSong(Guid id)
        {
            if (mPlaylist.First().Key == id)
            {
                mEndSong = DateTime.Now;
                return;
            }
            Queue<KeyValuePair<Guid, SongData>> newList = new Queue<KeyValuePair<Guid, SongData>>();
            foreach (var song in mPlaylist)
            {
                if (song.Key != id)
                {
                    newList.Enqueue(song);
                }
            }
            mPlaylist = newList;
        }
        public override XElement GetPlaylist()
        {
            XElement root = base.GetPlaylist();
            root.Add(new XAttribute("timeout", (int)(mEndSong - DateTime.Now).TotalSeconds));
            foreach (KeyValuePair<Guid,SongData> x in mPlaylist)
            {
                root.Add(new XElement("song",
                                      new XElement("track",
                                          new XAttribute("url", x.Value.TitleUrl),
                                          new XAttribute("duration", x.Value.Duration.ToString()),
                                          new XAttribute("guid", x.Key.ToString()),
                                          x.Value.Title),
                                      new XElement("artist",
                                          new XAttribute("url", x.Value.ArtistUrl),
                                          x.Value.Artist),
                                      new XElement("album",
                                          new XAttribute("url", x.Value.AlbumUrl),
                                          x.Value.Album),
                                      new XElement("image",
                                          new XAttribute("small", x.Value.SmallImageUrl),
                                          new XAttribute("medium", x.Value.MediumImageUrl),
                                          new XAttribute("large", x.Value.LargeImageUrl))
                                      ));
            }
            return root;
        }
    }
    /// <summary>
    /// The commercial state is when the jukebox is playing a commercial.
    /// Once the commercial is done then the jukebox will return to the previous state
    /// </summary>
    public class Commercial : State
    {
        public Commercial(JukeboxInstance jukebox) : base(jukebox) { }
        Playing mBack = null;
        DateTime mEndCommercial = DateTime.Now;
        CommercialData mCommercial;
        public Commercial(JukeboxInstance jukebox, Playing back)
            : base(jukebox)
        {
            mBack = back;
        }
        public override Guid? AddSong(SongData song)
        {
            if (mBack == null)
            {
                mBack = new Playing(mJukebox);
            }
            return mBack.AddSong(song);
        }
        public override void AddCommercial(CommercialData commercial)
        {
            mCommercial = commercial;
            mEndCommercial = DateTime.Now + commercial.Duration;
        }
        public override XElement GetPlaylist()
        {
            XElement playlist = base.GetPlaylist();
            if (mBack != null)
            {
                playlist = mBack.GetPlaylist();
                playlist.RemoveAttributes();
            }
            playlist.Add(new XAttribute("timeout", (int)(mEndCommercial - DateTime.Now).TotalSeconds));
            playlist.AddFirst(new XElement("commercial", mCommercial.Title));
            return playlist;
        }
        public override bool Update()
        {
            if (mEndCommercial > DateTime.Now) return true;
            if (mBack == null)
            {
                mJukebox.ChangeState(new Idle(mJukebox));
            }
            else
            {
                mJukebox.ChangeState(mBack);
            }
            return true;
        }
        public override void CancelSong(Guid id)
        {
            if (mBack != null) mBack.CancelSong(id);
        }
        public override void CancelCommercial()
        {
            mEndCommercial = DateTime.Now;
        }
    }
    public class Stopping : State
    {
        public Stopping(JukeboxInstance jukebox) : base(jukebox) { }
        public override bool Update() { return false; }
    }
}